package com.mrzhou.web.util;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * @author qiang.zhou
 */
@Slf4j
public class FileUtil {

    /**
     *  加载Resource下的配置文件
     * @param path Resource下的文件路径
     * @return
     */
    @SneakyThrows
    public static String loadSetting(String path) {
        Resource resource = new ClassPathResource(path);
        BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
        StringBuilder sb = new StringBuilder();
        String line;
        while((line = reader.readLine()) != null) {
            sb.append(line);
        }
        reader.close();
        return sb.toString();
    }

    public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, Set<Class<?>> classes) {
        // 获取此包的目录 建立一个File
        File dir = new File(packagePath);
        // 如果不存在或者 也不是目录就直接返回
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }

        // 如果存在 就获取包下的所有文件 包括目录
        File[] dirFiles = dir.listFiles((file) -> {
            // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
            return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
        });

        for(File file : dirFiles) {
            if(file.isDirectory()) {
                findAndAddClassesInPackageByFile(packageName + "."+ file.getName(), file.getAbsolutePath(), recursive, classes);
            } else {
                // 如果是java类文件 去掉后面的.class 只留下类名
                String className = file.getName().substring(0, file.getName().length() - 6);
                try {
                    classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + "." + className));
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void findAndAddClassesInJarByFile(URL url, String packageName, String packageDirName, final boolean recursive, Set<Class<?>> classes) {
        JarFile jar = null;
        // 获取jar
        try {
            jar = ((JarURLConnection) url.openConnection()).getJarFile();
            // 从此jar包 得到一个枚举类
            Enumeration<JarEntry> entries = jar.entries();
            while (entries.hasMoreElements()) {
                JarEntry jarEntry = entries.nextElement(); // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
                String name = jarEntry.getName();
                // 如果是以/开头的
                if (name.charAt(0) == '/') {
                    // 获取后面的字符串
                    name = name.substring(1);
                }
                // 如果前半部分和定义的包名相同
                if (name.startsWith(packageDirName)) {
                    int idx = name.lastIndexOf('/');
                    // 如果以"/"结尾 是一个包
                    if (idx != -1) {
                        // 获取包名 把'/'替换成'.'
                        packageName = name.substring(0, idx).replace('/', '.');
                    }
                    // 如果可以迭代下去 并且是一个包
                    if ((idx != -1 || recursive)) {
                        // 如果是一个.class文件 而且不是目录
                        if (name.endsWith(".class") && !jarEntry.isDirectory()) {
                            String className = name.substring(packageName.length() + 1, name.length() - 6);
                            // 添加到classes
                            try {
                                classes.add(Class.forName(packageName + '.' + className));
                            } catch (ClassNotFoundException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private List<File> fileList = new ArrayList<>(); // 递归保存文件
    private List<File> directoryFiles = new ArrayList<>(); // 递归保存文件目录

    /**
     * 根据传入参数来过滤父级目录下的第一级子路径下的文件目录
     * @param parentPath 父级目录
     * @param mark true表示过滤掉文件,保存目录; false表示过滤掉目录, 保存文件
     * @return
     */
    public static List<File> filterFile(String parentPath, boolean mark) {
        List<File> fileList = new ArrayList<File>();
        File parentFile = new File(parentPath);
        // 文件路径是否存在
        if(parentFile.exists()) {
            for(File file :parentFile.listFiles()) {
                if(mark) {
                    if(file.isDirectory()) { // 判断file是否为目录
                        fileList.add(file);
                    }
                } else {
                    if(file.isFile()) { // 判断file是否为文件
                        fileList.add(file);
                    }
                }
            }
        }
        return fileList;
    }

    /**
     * 根据传入参数来过滤父级目录下的第一级路径下的文件目录
     * @param parentFile 父级目录
     * @param mark true表示过滤掉文件,保存目录; false表示过滤掉目录, 保存文件
     * @return
     */
    public static List<File> filterFile(File parentFile, boolean mark) {
        List<File> fileList = new ArrayList<File>();
        // 文件路径是否存在
        if(parentFile.exists()) {
            for(File file :parentFile.listFiles()) {
                if(mark) {
                    if(file.isDirectory()) { // 判断file是否为目录
                        fileList.add(file);
                    }
                } else {
                    if(file.isFile()) { // 判断file是否为文件
                        fileList.add(file);
                    }
                }
            }
        }
        return fileList;
    }

    /**
     * 以字节流的方式响应到客户端
     * @param file 需下载的文件
     * @param response
     */
    public static void fileUploadStream(File file, HttpServletResponse response) {
        BufferedInputStream bis = null;
        OutputStream os = null;
        if(file.exists()) {
            String fileName = file.getName();
            try {
                // 配置响应给客户端的请求头, 以字节流的方式响应
                response.setHeader("content-type", "application/force-download");
                response.setContentType("application/force-download");
                // 下载文件能正常显示中文
                response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));

                // 通过java的io流实现文件下载
                bis = new BufferedInputStream(new FileInputStream(file));
                os = response.getOutputStream();
                byte[] buffer = new byte[1024];
                int i = bis.read(buffer);
                while(i != -1) {
                    os.write(buffer, 0, i);
                    i = bis.read(buffer);
                }
            } catch (Exception e) {
                e.printStackTrace();
                log.error("文件下载失败"+e);
            } finally {
                if( os != null) {
                    try {
                        os.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                        log.error("关流失败, 原因: "+e);
                    }
                }
                if(bis != null) {
                    try {
                        bis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                        log.error("关流失败, 原因: "+e);
                    }
                }
            }
        }
    }

    /**
     * 获得指定父级文件路径下的子文件夹及文件名
     * @param parentFilePath
     * @return
     */
    public Map<String, Object> getChildFileName(String parentFilePath) {
        Map<String, Object> map = new HashMap<>();
        List<String> fileNameList = new ArrayList<>(); // 保存文件夹下的子文件
        List<File> directoryNameList = new ArrayList<>(); // 保存文件夹下的子目录
        File parentFile = new File(parentFilePath);
        if(parentFile.exists()) {
            for(File chileFile: parentFile.listFiles()) {
                if(chileFile.isFile()) {
                    fileNameList.add(chileFile.getName());
                } else {
                    directoryNameList.add(chileFile);
                }
            }
        }
        map.put("fileNameList", fileNameList);
        map.put("directoryNameList", directoryNameList);
        return map;
    }

    /**
     * 递归获得指定文件夹下的全部文件及子目录
     * @param parentFile
     */
    public void getChildFileList(File parentFile) {
        if(parentFile.exists()) {
            File[] files = parentFile.listFiles();
            if(files == null)
                return ;
            for(File file : files){
                if(file.isFile()) {
                    fileList.add(file);
                } else {
                    directoryFiles.add(file);
                    getChildFileList(file);
                }
            }
        }
    }

    /**
     * 递归获得指定文件夹下的全部文件及子目录
     * @param parentFile
     * @param isSaveDirectory true保存目录, false则不保存
     */
    public void getChildFileList(File parentFile, Boolean isSaveDirectory) {
        if(parentFile != null && parentFile.exists()) {
            File[] files = parentFile.listFiles();
            if(files == null)
                return ;
            for(File file : files){
                if(file.isFile()) {
                    fileList.add(file);
                } else {
                    if(isSaveDirectory) {
                        directoryFiles.add(file);
                    }
                    getChildFileList(file,isSaveDirectory);
                }
            }
        }
    }

    /**
     * 递归查询文件夹下中匹配的所有名称相同的文件
     * @param parentFile
     * @param fileName
     */
    public void queryFileOnParentFile(File parentFile,String fileName) {
        if(parentFile.exists()) {
            File[] files = parentFile.listFiles();
            if(files == null)
                return ;
            for(File file: files) {
                if(file.isFile()) {
                    if(file.getName().equals(fileName)) {
                        fileList.add(file);
                    }
                } else {
                    queryFileOnParentFile(file,fileName);
                }
            }
        }
    }

    /**
     * 解压缩文件
     * @param zipPath 压缩文件的地址
     * @param targetPath 目标文件
     * @param flag 是否已压缩文件名称作为解压之后的文件名称
     */
    public static void unZip(String zipPath, String targetPath, boolean flag) {
        try {
            Long startTime = System.currentTimeMillis();
            // 是否以压缩文件名称作为解压后的文件夹
            File temp = new File(zipPath);
            if (flag) {
                targetPath = targetPath + File.separator + temp.getName().substring(0, temp.getName().lastIndexOf("."));
            }

            // 解压文件
            ZipFile zipFile = new ZipFile(temp);
            Enumeration<?> entries = zipFile.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = (ZipEntry) entries.nextElement();
                File file = new File(targetPath + File.separator + entry.getName());
                if (!file.exists()) {
                    if (entry.isDirectory()) {
                        file.mkdirs();
                    } else if (!file.getParentFile().exists()) {
                        file.getParentFile().mkdirs();
                    } else {
                        file.createNewFile();
                    }
                }
                if (!entry.isDirectory()) {
                    readToWirte(zipFile.getInputStream(entry), targetPath + File.separator + entry.getName());
                }
            }
            zipFile.close();
            System.out.println("解压时间: " + (System.currentTimeMillis() - startTime));
        } catch (IOException e) {
            log.error("解压文件失败; 文件路径{}, 失败原因: {}", zipPath, e);
        }

    }

    public static void unZip(String zipPath, String targetPath) {
        unZip(zipPath, targetPath, false);
    }

    /**
     * 递归删除文件夹
     * @param baseFile
     */
    public static void deletedChildFile(File baseFile) {
        File[] files = baseFile.listFiles();

        if (null != files && files.length > 0) {
            for (File file: files) {
                if (file.isDirectory()) {
                    deletedChildFile(file);
                }
                file.delete();
            }
        }
    }

    /**
     * 删除文件, 包括自身
     * @param basePath
     */
    public static void deletedFile(String basePath) {
        File file = new File(basePath);
        deletedChildFile(new File(basePath));
        file.delete();
    }

    /**
     * 将input流 写
     * @param is
     * @param filePath
     * @throws IOException
     */
    private static void readToWirte(InputStream is, String filePath) throws IOException {
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));

        byte[] bytes = new byte[1024];
        int i;
        while ((i = is.read(bytes)) != -1) {
            bos.write(bytes, 0, i);
        }
        bos.close();
    }

}
